Skip to content

Add template time helpers#180

Merged
leynos merged 9 commits into
mainfrom
codex/implement-time-helpers-in-template-library
Oct 8, 2025
Merged

Add template time helpers#180
leynos merged 9 commits into
mainfrom
codex/implement-time-helpers-in-template-library

Conversation

@leynos

@leynos leynos commented Oct 5, 2025

Copy link
Copy Markdown
Owner

Summary

  • add time helper implementations for now and timedelta, including ISO-8601 formatting and offset parsing
  • cover the helpers with unit tests and cucumber scenarios exercising success and failure cases
  • document the helpers in the design guide and mark the roadmap item complete

Testing

  • make fmt
  • make lint
  • make test

https://chatgpt.com/codex/tasks/task_e_68e2edccc8708322b3d8600dfb9c6c77

Summary by Sourcery

Add deterministic time template functions now() and timedelta() to the stdlib, with ISO-8601 formatting, offset parsing, documentation updates, and comprehensive tests

New Features:

  • Add now() helper to capture current time in templates
  • Add timedelta() helper to construct ISO-8601 durations

Enhancements:

  • Implement ISO-8601 timestamp and duration formatting with offset parsing
  • Expose iso8601, unix_timestamp, offset, seconds, and nanoseconds attributes on time objects

Build:

  • Add time crate dependency with formatting, parsing, and serde features

Documentation:

  • Document time helpers in design guide and mark roadmap item complete

Tests:

  • Add unit tests and cucumber scenarios for success and error cases of now() and timedelta() helpers
  • Refactor Cucumber step definitions to support context and new assertions

@sourcery-ai

sourcery-ai Bot commented Oct 5, 2025

Copy link
Copy Markdown
Contributor

Reviewer's Guide

This PR adds deterministic time primitives (now and timedelta) to the template stdlib, implementing ISO-8601 formatting, offset parsing, and duration arithmetic in Rust, along with full test coverage and documentation updates.

Class diagram for new time helper types (TimestampValue and TimeDeltaValue)

classDiagram
    class TimestampValue {
        +OffsetDateTime datetime
        +iso8601() String
    }
    class TimeDeltaValue {
        +Duration duration
        +iso8601() String
    }
    TimestampValue --|> Object
    TimeDeltaValue --|> Object
    Object <|.. TimestampValue
    Object <|.. TimeDeltaValue
    TimestampValue : +get_value(key: Value) Option<Value>
    TimeDeltaValue : +get_value(key: Value) Option<Value>
Loading

Class diagram for new time helper registration in stdlib

classDiagram
    class Environment {
        +add_function(name: String, func: Fn)
    }
    class TimeHelpers {
        +register_functions(env: Environment)
        +now(kwargs: Kwargs) Result<Value, Error>
        +timedelta(kwargs: Kwargs) Result<Value, Error>
    }
    Environment <|.. TimeHelpers
Loading

File-Level Changes

Change Details Files
Implement now and timedelta template helpers with ISO8601 formatting and offset parsing
  • parse and apply optional offset in now()
  • accumulate duration components with overflow checks in timedelta()
  • format timestamps and durations according to ISO-8601 grammar
  • expose .iso8601, .unix_timestamp, .offset and duration attributes
src/stdlib/time/mod.rs
src/stdlib/mod.rs
Cover now and timedelta helpers with unit and Cucumber integration tests
  • add unit tests for default behaviors, offsets, and error conditions
  • add Cucumber feature scenarios exercising success and failure cases
  • extend test steps to support custom context rendering and parse/assert ISO8601 timestamps and offsets
src/stdlib/time/tests.rs
tests/features/stdlib_time.feature
tests/steps/stdlib_steps.rs
Document time helpers in design guide and mark roadmap completion
  • describe helper behavior, available accessors, and usage examples in netsuke-design.md
  • mark the time helpers item as implemented in roadmap.md
docs/netsuke-design.md
docs/roadmap.md
Add time crate dependency with necessary features
  • include the time crate in Cargo.toml
  • enable formatting, parsing, macros, and serde features
Cargo.toml

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Oct 5, 2025

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@leynos has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 0 minutes and 49 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between fc3f8b8 and 9cb2d0a.

📒 Files selected for processing (1)
  • src/stdlib/time/mod.rs (1 hunks)

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Summary by CodeRabbit

  • New Features
    • Introduced time helpers for templates: now([offset]) returns the current time (UTC by default) with ISO8601/unix timestamp/offset accessors; timedelta(...) constructs precise durations (weeks to nanoseconds), supports negatives, and validates inputs with clear errors.
  • Documentation
    • Added detailed guidance and examples for using now and timedelta; updated the roadmap to mark them complete.
  • Chores
    • Updated time-related dependency to enable formatting, parsing, macros, and serialisation features.

Walkthrough

Add a runtime dependency on time. Introduce a stdlib time module registering now and timedelta. Add documentation and roadmap updates. Add unit tests, feature tests and test utilities to validate timestamps, offsets, durations and error cases.

Changes

Cohort / File(s) Summary
Dependencies
Cargo.toml
Add runtime dependency on time with features: formatting, macros, parsing, serde. Update build-dependency time to 0.3.44 with formatting.
Documentation
docs/netsuke-design.md, docs/roadmap.md
Document now() and timedelta() helpers, attributes and examples. Mark roadmap items for time helpers as complete.
Stdlib Integration
src/stdlib/mod.rs
Declare mod time; and call time::register_functions(env) during stdlib registration.
Time Helpers Implementation
src/stdlib/time/mod.rs
Implement now (UTC by default, optional offset parsing) and timedelta (components weeks→nanoseconds, supports negatives). Add TimestampValue and TimeDeltaValue with attributes (iso8601, unix_timestamp, offset, seconds, nanoseconds). Include ISO8601 formatting, validation and overflow error handling.
Unit Tests
src/stdlib/time/tests.rs
Add tests covering now() defaults/offsets/errors, timedelta() accumulation/negatives/overflow and ISO8601 rendering.
Feature Tests
tests/features/stdlib_time.feature
Add BDD scenarios verifying now() and timedelta() rendering, offsets, precision and error handling.
Test Scaffolding & Utilities
tests/steps/stdlib_steps.rs
Refactor render helpers to accept value::Value contexts. Add ISO8601 parsing and offset utilities and assertions for UTC and custom offsets.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Template
  participant Env as Environment
  participant Time as Stdlib::time
  participant Tc as time crate

  Note over Template,Env: Render evaluates template expression using stdlib time helpers
  Template->>Env: eval "{{ now(offset='+02:30').iso8601 }}"
  Env->>Time: now(offset="+02:30")
  Time->>Tc: OffsetDateTime::now_utc()
  Time->>Tc: parse offset "+02:30"
  Tc-->>Time: UtcOffset(+02:30)
  Time-->>Env: TimestampValue{iso8601, unix_timestamp, offset}
  Env-->>Template: "2025-10-05T12:34:56+02:30"

  rect rgb(230,245,255)
    Note right of Time: New stdlib helper registration and ISO8601 formatting logic
  end
Loading
sequenceDiagram
  autonumber
  actor Template
  participant Env as Environment
  participant Time as Stdlib::time

  Template->>Env: eval "{{ timedelta(days=1, minutes=90).iso8601 }}"
  Env->>Time: timedelta({days:1, minutes:90})
  Time->>Time: accumulate components -> Duration
  Time-->>Env: TimeDeltaValue{iso8601, seconds, nanoseconds}
  Env-->>Template: "P1DT1H30M"
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

New clocks in the library chime,
Offsets stride and mark the time.
Durations dance in ISO light,
Nanos wink and hold on tight.
Tests stand watch until the merge takes flight. ⏱️✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.35% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title succinctly and accurately summarises the core change of adding deterministic time helpers to the template library by highlighting the introduction of now() and timedelta() functions without extraneous detail.
Description Check ✅ Passed The description clearly outlines the implementation of the now() and timedelta() helpers, their ISO-8601 formatting and offset parsing, associated tests, documentation updates and build changes, which directly corresponds to the changeset content.

Warning

Review ran into problems

🔥 Problems

Errors were encountered while retrieving linked issues.

Errors (1)
  • ISO-8601: Entity not found: Issue - Could not find referenced Issue.

Comment @coderabbitai help to get the list of available commands and usage tips.

codescene-delta-analysis[bot]

This comment was marked as outdated.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes and they look great!

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location> `src/stdlib/time/tests.rs:63` </location>
<code_context>
+#[rstest]
+fn now_rejects_invalid_offset() {
</code_context>

<issue_to_address>
**suggestion (testing):** Consider adding tests for additional invalid offset formats.

Please include cases like missing sign, out-of-range values (e.g., '+25:00'), malformed strings (e.g., '+01:60', '+01:01:61'), and empty strings to improve coverage of invalid offsets.
</issue_to_address>

### Comment 2
<location> `src/stdlib/time/tests.rs:97` </location>
<code_context>
+#[rstest]
+fn timedelta_supports_negative_values() {
</code_context>

<issue_to_address>
**suggestion (testing):** Test negative values for all timedelta components.

Please add tests for negative days, weeks, minutes, milliseconds, microseconds, and nanoseconds to ensure all components are covered.
</issue_to_address>

### Comment 3
<location> `src/stdlib/time/tests.rs:120` </location>
<code_context>
+#[rstest]
+fn timestamp_iso8601_property() {
</code_context>

<issue_to_address>
**suggestion (testing):** Consider adding tests for ISO8601 formatting with offsets and fractional seconds.

Please include test cases for timestamps with non-UTC offsets and fractional seconds to fully validate ISO8601 formatting.
</issue_to_address>

### Comment 4
<location> `src/stdlib/time/tests.rs:132` </location>
<code_context>
+#[rstest]
+fn timedelta_iso8601_property() {
</code_context>

<issue_to_address>
**suggestion (testing):** Add tests for ISO8601 formatting of negative and zero durations.

Please include test cases for negative and zero durations to ensure correct ISO8601 formatting behavior.
</issue_to_address>

### Comment 5
<location> `src/stdlib/time/mod.rs:68` </location>
<code_context>
+    Ok(Value::from_object(TimeDeltaValue::new(total)))
+}
+
+fn parse_offset(raw: &str) -> Result<UtcOffset, Error> {
+    let trimmed = raw.trim();
+    if trimmed.eq_ignore_ascii_case("z") {
</code_context>

<issue_to_address>
**issue (complexity):** Consider using time crate's built-in parsers/formatters and merging the two add helpers into one generic function to simplify the code.

```markdown
I think we can shave off quite a bit of custom logic by leaning on ‑time-’s own parsers/formatters and merging the two “add_…” helpers into one. For example:

1) Replace your manual `parse_offset` with a single call to `UtcOffset::parse` + a small `format_description`.  
```rust
use time::format_description::FormatItem;
use time::macros::format_description;
static OFFSET_FMT: &[FormatItem<'_>] = format_description!(
    // +HH:MM or -HH:MM or +HH:MM:SS (seconds optional)
    "[offset_sign][offset_hour]:[offset_minute][optional: [:offset_second]]"
);

fn parse_offset(raw: &str) -> Result<UtcOffset, Error> {
    UtcOffset::parse(raw.trim(), OFFSET_FMT)
        .map_err(|_| invalid_offset(raw))
}
```

2) Collapse `add_seconds_component`/`add_nanoseconds_component` into one generic helper:  
```rust
fn add_component(
    mut total: Duration,
    amount: Option<i64>,
    mul: i64,
    mk: fn(i64) -> Duration,
    label: &str,
) -> Result<Duration, Error> {
    if let Some(v) = amount {
        let v = v.checked_mul(mul).ok_or_else(|| overflow_error(label))?;
        total = total
            .checked_add(mk(v))
            .ok_or_else(|| overflow_error(label))?;
    }
    Ok(total)
}

// usage in `timedelta`:
total = add_component(total, weeks,   SECONDS_PER_WEEK,        Duration::seconds,    "weeks")?;
total = add_component(total, nanosec, NANOS_PER_MILLISECOND,    Duration::nanoseconds, "milliseconds")?;
```

This removes ~60 lines of duplication, keeps all your overflow/error‐handling, and pushes complex string‐massaging into `time` itself.
</issue_to_address>

### Comment 6
<location> `docs/netsuke-design.md:937` </location>
<code_context>

+The `now()` helper produces an object that renders as an ISO&nbsp;8601
+timestamp and exposes `iso8601`, `unix_timestamp`, and `offset` accessors so
+templates can serialise or compare values without string parsing. It defaults
+to UTC but accepts an `offset="+HH:MM"` keyword argument that re-bases the
+captured time on another fixed offset. Time is captured lazily when the helper
</code_context>

<issue_to_address>
**suggestion (review_instructions):** The word 'serialise' should use en-oxendic spelling: 'serialize'.

Please change 'serialise' to 'serialize' to conform to en-oxendic spelling conventions.

<details>
<summary>Review instructions:</summary>

**Path patterns:** `**/*.md`

**Instructions:**
Use en-oxendic (-ize / -yse / -our) spelling and grammar.

</details>
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/stdlib/time/tests.rs
Comment thread src/stdlib/time/tests.rs Outdated
Comment thread src/stdlib/time/tests.rs Outdated
Comment thread src/stdlib/time/tests.rs Outdated
Comment thread src/stdlib/time/mod.rs
Comment thread docs/netsuke-design.md Outdated
@leynos

leynos commented Oct 5, 2025

Copy link
Copy Markdown
Owner Author

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix:

src/stdlib/time/mod.rs

Comment on lines +68 to +125

fn parse_offset(raw: &str) -> Result<UtcOffset, Error> {
    let trimmed = raw.trim();
    if trimmed.eq_ignore_ascii_case("z") {
        return Ok(UtcOffset::UTC);
    }

    let (sign, rest) = if let Some(remaining) = trimmed.strip_prefix('+') {
        (1_i64, remaining)
    } else if let Some(remaining) = trimmed.strip_prefix('-') {
        (-1_i64, remaining)
    } else {
        return Err(invalid_offset(raw));
    };

    let (hours_part, remaining) = rest.split_once(':').ok_or_else(|| invalid_offset(raw))?;
    if hours_part.contains(':') {
        return Err(invalid_offset(raw));
    }

    let (minutes_part, seconds_part) = match remaining.split_once(':') {
        Some((mins, secs)) if !secs.contains(':') => (mins, Some(secs)),
        Some(_) => return Err(invalid_offset(raw)),
        None => (remaining, None),
    };

    if minutes_part.contains(':') {
        return Err(invalid_offset(raw));
    }

    let hours = parse_component(hours_part, raw)?;
    let minutes = parse_component(minutes_part, raw)?;
    let seconds = seconds_part
        .map(|value| parse_component(value, raw))
        .transpose()?;

    if !(0..=23).contains(&hours) || !(0..=59).contains(&minutes) {
        return Err(invalid_offset(raw));
    }

    let seconds_value = seconds.unwrap_or_default();
    if !(0..=59).contains(&seconds_value) {
        return Err(invalid_offset(raw));
    }

    let total_seconds = sign
        * (i64::from(hours) * SECONDS_PER_HOUR
            + i64::from(minutes) * SECONDS_PER_MINUTE
            + i64::from(seconds_value));

    let total_seconds = i32::try_from(total_seconds).map_err(|_| invalid_offset(raw))?;

    UtcOffset::from_whole_seconds(total_seconds).map_err(|err| {
        Error::new(
            ErrorKind::InvalidOperation,
            format!("now offset '{raw}' is invalid: {err}"),
        )
    })
}

❌ New issue: Complex Method
parse_offset has a cyclomatic complexity of 12, threshold = 9

@leynos

leynos commented Oct 5, 2025

Copy link
Copy Markdown
Owner Author

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix:

src/stdlib/time/mod.rs

Comment on lines +217 to +269

fn format_duration_iso8601(duration: Duration) -> String {
    if duration.is_zero() {
        return "PT0S".to_owned();
    }

    let mut buffer = String::new();
    if duration.is_negative() {
        buffer.push('-');
    }
    buffer.push('P');

    let absolute = duration.abs();
    let days = absolute.whole_days();
    let mut remainder = absolute - Duration::days(days);

    if days != 0 {
        buffer.push_str(&days.to_string());
        buffer.push('D');
    }

    let mut time_section = String::new();

    let hours = remainder.whole_hours();
    if hours != 0 {
        time_section.push_str(&hours.to_string());
        time_section.push('H');
        remainder -= Duration::hours(hours);
    }

    let minutes = remainder.whole_minutes();
    if minutes != 0 {
        time_section.push_str(&minutes.to_string());
        time_section.push('M');
        remainder -= Duration::minutes(minutes);
    }

    let seconds = remainder.whole_seconds();
    let nanos = remainder.subsec_nanoseconds();
    if seconds != 0 || nanos != 0 {
        time_section.push_str(&format_seconds_with_fraction(seconds, nanos));
    }

    if time_section.is_empty() {
        if buffer.ends_with('P') {
            buffer.push_str("T0S");
        }
    } else {
        buffer.push('T');
        buffer.push_str(&time_section);
    }

    buffer
}

❌ New issue: Complex Method
format_duration_iso8601 has a cyclomatic complexity of 10, threshold = 9

@leynos

leynos commented Oct 5, 2025

Copy link
Copy Markdown
Owner Author

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix:

src/stdlib/time/tests.rs

Comment on lines +124 to +133

fn timedelta_iso8601_property() {
    let duration = Duration::seconds(SECONDS_PER_DAY + 30) + Duration::nanoseconds(500_000_000);
    let value = Value::from_object(TimeDeltaValue::new(duration));
    let object = value.as_object().expect("object");
    let iso = object
        .get_value(&Value::from("iso8601"))
        .expect("iso8601 attr")
        .to_string();
    assert_eq!(iso, "P1DT30.5S");
}

❌ New issue: Code Duplication
The module contains 2 functions with similar structure: timedelta_iso8601_property,timestamp_iso8601_property

@coderabbitai

This comment was marked as resolved.

@coderabbitai

This comment was marked as resolved.

@coderabbitai

This comment was marked as resolved.

codescene-delta-analysis[bot]

This comment was marked as outdated.

codescene-delta-analysis[bot]

This comment was marked as outdated.

codescene-delta-analysis[bot]

This comment was marked as outdated.

- rely on time's format_description parser for offsets and share a generic component accumulator to remove timedelta duplication\n- broaden time helper coverage with invalid offset cases, negative components, and ISO-8601 assertions for offsets, fractions, and zero/negative durations\n- align documentation spelling with en-oxendic guidance

@codescene-delta-analysis codescene-delta-analysis Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gates Failed
Enforce advisory code health rules (1 file with Excess Number of Function Arguments)

Gates Passed
5 Quality Gates Passed

See analysis details in CodeScene

Reason for failure
Enforce advisory code health rules Violations Code Health Impact
mod.rs 1 advisory rule 9.69 Suppress

Quality Gate Profile: Pay Down Tech Debt
Want more control? Customize Code Health rules or catch issues early with our IDE extension and CLI tool.

@leynos

leynos commented Oct 8, 2025

Copy link
Copy Markdown
Owner Author

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix:

src/stdlib/time/mod.rs

Comment on lines +110 to +127

fn add_component(
    mut total: Duration,
    amount: Option<i64>,
    multiplier: i64,
    constructor: fn(i64) -> Duration,
    label: &str,
) -> Result<Duration, Error> {
    if let Some(value) = amount {
        let scaled = value
            .checked_mul(multiplier)
            .ok_or_else(|| overflow_error(label))?;
        let component = constructor(scaled);
        total = total
            .checked_add(component)
            .ok_or_else(|| overflow_error(label))?;
    }
    Ok(total)
}

❌ New issue: Excess Number of Function Arguments
add_component has 5 arguments, max arguments = 4

@coderabbitai

coderabbitai Bot commented Oct 8, 2025

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Read the full file to understand how add_component is used
cat src/stdlib/time/mod.rs

Length of output: 9454


@leynos, I'll suggest a refactoring that groups related parameters into a struct to reduce the argument count from 5 to 3.

Suggested Fix

Introduce a ComponentSpec struct to group the multiplier, constructor, and label parameters, which are conceptually related:

struct ComponentSpec {
    multiplier: i64,
    constructor: fn(i64) -> Duration,
    label: &'static str,
}

fn add_component(
    mut total: Duration,
    amount: Option<i64>,
    spec: ComponentSpec,
) -> Result<Duration, Error> {
    if let Some(value) = amount {
        let scaled = value
            .checked_mul(spec.multiplier)
            .ok_or_else(|| overflow_error(spec.label))?;
        let component = (spec.constructor)(scaled);
        total = total
            .checked_add(component)
            .ok_or_else(|| overflow_error(spec.label))?;
    }
    Ok(total)
}

Update the call sites in the timedelta function:

fn timedelta(kwargs: &Kwargs) -> Result<Value, Error> {
    let weeks: Option<i64> = kwargs.get("weeks")?;
    let days: Option<i64> = kwargs.get("days")?;
    let hours: Option<i64> = kwargs.get("hours")?;
    let minutes: Option<i64> = kwargs.get("minutes")?;
    let seconds: Option<i64> = kwargs.get("seconds")?;
    let milliseconds: Option<i64> = kwargs.get("milliseconds")?;
    let microseconds: Option<i64> = kwargs.get("microseconds")?;
    let nanoseconds: Option<i64> = kwargs.get("nanoseconds")?;
    kwargs.assert_all_used()?;

    let mut total = Duration::ZERO;
    total = add_component(total, weeks, ComponentSpec {
        multiplier: SECONDS_PER_WEEK,
        constructor: Duration::seconds,
        label: "weeks",
    })?;
    total = add_component(total, days, ComponentSpec {
        multiplier: SECONDS_PER_DAY,
        constructor: Duration::seconds,
        label: "days",
    })?;
    total = add_component(total, hours, ComponentSpec {
        multiplier: SECONDS_PER_HOUR,
        constructor: Duration::seconds,
        label: "hours",
    })?;
    total = add_component(total, minutes, ComponentSpec {
        multiplier: SECONDS_PER_MINUTE,
        constructor: Duration::seconds,
        label: "minutes",
    })?;
    total = add_component(total, seconds, ComponentSpec {
        multiplier: 1,
        constructor: Duration::seconds,
        label: "seconds",
    })?;
    total = add_component(total, milliseconds, ComponentSpec {
        multiplier: NANOS_PER_MILLISECOND,
        constructor: Duration::nanoseconds,
        label: "milliseconds",
    })?;
    total = add_component(total, microseconds, ComponentSpec {
        multiplier: NANOS_PER_MICROSECOND,
        constructor: Duration::nanoseconds,
        label: "microseconds",
    })?;
    total = add_component(total, nanoseconds, ComponentSpec {
        multiplier: 1,
        constructor: Duration::nanoseconds,
        label: "nanoseconds",
    })?;

    Ok(Value::from_object(TimeDeltaValue::new(total)))
}

This reduces the argument count from 5 to 3, well below the threshold of 4.

AI Coding Agent Prompt

In the file `src/stdlib/time/mod.rs`, refactor the `add_component` function to reduce its argument count from 5 to 3 by grouping related parameters into a struct.

Add a new `ComponentSpec` struct immediately before the `add_component` function (before line 110):

```rust
struct ComponentSpec {
    multiplier: i64,
    constructor: fn(i64) -> Duration,
    label: &'static str,
}
```

Replace the `add_component` function (lines 110-127) with:

```rust
fn add_component(
    mut total: Duration,
    amount: Option<i64>,
    spec: ComponentSpec,
) -> Result<Duration, Error> {
    if let Some(value) = amount {
        let scaled = value
            .checked_mul(spec.multiplier)
            .ok_or_else(|| overflow_error(spec.label))?;
        let component = (spec.constructor)(scaled);
        total = total
            .checked_add(component)
            .ok_or_else(|| overflow_error(spec.label))?;
    }
    Ok(total)
}
```

Update all 8 call sites in the `timedelta` function (lines 67-96) to use the new struct-based API. Replace each call like:

```rust
total = add_component(total, weeks, SECONDS_PER_WEEK, Duration::seconds, "weeks")?;
```

with:

```rust
total = add_component(total, weeks, ComponentSpec {
    multiplier: SECONDS_PER_WEEK,
    constructor: Duration::seconds,
    label: "weeks",
})?;
```

Apply this pattern to all 8 calls (weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds), matching the existing multiplier, constructor, and label values for each component.

Ensure all existing tests continue to pass after the refactoring.

codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos

leynos commented Oct 8, 2025

Copy link
Copy Markdown
Owner Author

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix:

src/stdlib/time/mod.rs

Comment on lines +52 to +138

fn timedelta(kwargs: &Kwargs) -> Result<Value, Error> {
    let weeks: Option<i64> = kwargs.get("weeks")?;
    let days: Option<i64> = kwargs.get("days")?;
    let hours: Option<i64> = kwargs.get("hours")?;
    let minutes: Option<i64> = kwargs.get("minutes")?;
    let seconds: Option<i64> = kwargs.get("seconds")?;
    let milliseconds: Option<i64> = kwargs.get("milliseconds")?;
    let microseconds: Option<i64> = kwargs.get("microseconds")?;
    let nanoseconds: Option<i64> = kwargs.get("nanoseconds")?;
    kwargs.assert_all_used()?;

    let mut total = Duration::ZERO;
    total = add_component(
        total,
        weeks,
        ComponentSpec {
            multiplier: SECONDS_PER_WEEK,
            constructor: Duration::seconds,
            label: "weeks",
        },
    )?;
    total = add_component(
        total,
        days,
        ComponentSpec {
            multiplier: SECONDS_PER_DAY,
            constructor: Duration::seconds,
            label: "days",
        },
    )?;
    total = add_component(
        total,
        hours,
        ComponentSpec {
            multiplier: SECONDS_PER_HOUR,
            constructor: Duration::seconds,
            label: "hours",
        },
    )?;
    total = add_component(
        total,
        minutes,
        ComponentSpec {
            multiplier: SECONDS_PER_MINUTE,
            constructor: Duration::seconds,
            label: "minutes",
        },
    )?;
    total = add_component(
        total,
        seconds,
        ComponentSpec {
            multiplier: 1,
            constructor: Duration::seconds,
            label: "seconds",
        },
    )?;
    total = add_component(
        total,
        milliseconds,
        ComponentSpec {
            multiplier: NANOS_PER_MILLISECOND,
            constructor: Duration::nanoseconds,
            label: "milliseconds",
        },
    )?;
    total = add_component(
        total,
        microseconds,
        ComponentSpec {
            multiplier: NANOS_PER_MICROSECOND,
            constructor: Duration::nanoseconds,
            label: "microseconds",
        },
    )?;
    total = add_component(
        total,
        nanoseconds,
        ComponentSpec {
            multiplier: 1,
            constructor: Duration::nanoseconds,
            label: "nanoseconds",
        },
    )?;

    Ok(Value::from_object(TimeDeltaValue::new(total)))
}

❌ New issue: Large Method
timedelta has 85 lines, threshold = 70

@coderabbitai

coderabbitai Bot commented Oct 8, 2025

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Read the current state of the file to understand ComponentSpec and constants
cat src/stdlib/time/mod.rs

Length of output: 10425


@leynos, I'll suggest a refactoring that uses a data-driven approach to reduce the function from 85 lines to approximately 40 lines.

Suggested Fix

Replace the repetitive add_component calls with a static configuration array and iteration:

const COMPONENT_SPECS: &[(&str, ComponentSpec)] = &[
    (
        "weeks",
        ComponentSpec {
            multiplier: SECONDS_PER_WEEK,
            constructor: Duration::seconds,
            label: "weeks",
        },
    ),
    (
        "days",
        ComponentSpec {
            multiplier: SECONDS_PER_DAY,
            constructor: Duration::seconds,
            label: "days",
        },
    ),
    (
        "hours",
        ComponentSpec {
            multiplier: SECONDS_PER_HOUR,
            constructor: Duration::seconds,
            label: "hours",
        },
    ),
    (
        "minutes",
        ComponentSpec {
            multiplier: SECONDS_PER_MINUTE,
            constructor: Duration::seconds,
            label: "minutes",
        },
    ),
    (
        "seconds",
        ComponentSpec {
            multiplier: 1,
            constructor: Duration::seconds,
            label: "seconds",
        },
    ),
    (
        "milliseconds",
        ComponentSpec {
            multiplier: NANOS_PER_MILLISECOND,
            constructor: Duration::nanoseconds,
            label: "milliseconds",
        },
    ),
    (
        "microseconds",
        ComponentSpec {
            multiplier: NANOS_PER_MICROSECOND,
            constructor: Duration::nanoseconds,
            label: "microseconds",
        },
    ),
    (
        "nanoseconds",
        ComponentSpec {
            multiplier: 1,
            constructor: Duration::nanoseconds,
            label: "nanoseconds",
        },
    ),
];

fn timedelta(kwargs: &Kwargs) -> Result<Value, Error> {
    let mut total = Duration::ZERO;
    
    for (name, spec) in COMPONENT_SPECS {
        let amount: Option<i64> = kwargs.get(name)?;
        total = add_component(total, amount, *spec)?;
    }
    
    kwargs.assert_all_used()?;
    Ok(Value::from_object(TimeDeltaValue::new(total)))
}

This reduces the function to approximately 10 lines (well below the 70-line threshold) whilst maintaining identical behaviour.

AI Coding Agent Prompt

In the file `src/stdlib/time/mod.rs`, refactor the `timedelta` function to reduce its line count from 85 to under 70 by replacing repetitive code with a data-driven approach.

Add a new constant array before the `timedelta` function (after line 169, immediately before the ComponentSpec struct definition):

```rust
const COMPONENT_SPECS: &[(&str, ComponentSpec)] = &[
    (
        "weeks",
        ComponentSpec {
            multiplier: SECONDS_PER_WEEK,
            constructor: Duration::seconds,
            label: "weeks",
        },
    ),
    (
        "days",
        ComponentSpec {
            multiplier: SECONDS_PER_DAY,
            constructor: Duration::seconds,
            label: "days",
        },
    ),
    (
        "hours",
        ComponentSpec {
            multiplier: SECONDS_PER_HOUR,
            constructor: Duration::seconds,
            label: "hours",
        },
    ),
    (
        "minutes",
        ComponentSpec {
            multiplier: SECONDS_PER_MINUTE,
            constructor: Duration::seconds,
            label: "minutes",
        },
    ),
    (
        "seconds",
        ComponentSpec {
            multiplier: 1,
            constructor: Duration::seconds,
            label: "seconds",
        },
    ),
    (
        "milliseconds",
        ComponentSpec {
            multiplier: NANOS_PER_MILLISECOND,
            constructor: Duration::nanoseconds,
            label: "milliseconds",
        },
    ),
    (
        "microseconds",
        ComponentSpec {
            multiplier: NANOS_PER_MICROSECOND,
            constructor: Duration::nanoseconds,
            label: "microseconds",
        },
    ),
    (
        "nanoseconds",
        ComponentSpec {
            multiplier: 1,
            constructor: Duration::nanoseconds,
            label: "nanoseconds",
        },
    ),
];
```

Replace the entire `timedelta` function (lines 52-138) with:

```rust
fn timedelta(kwargs: &Kwargs) -> Result<Value, Error> {
    let mut total = Duration::ZERO;
    
    for (name, spec) in COMPONENT_SPECS {
        let amount: Option<i64> = kwargs.get(name)?;
        total = add_component(total, amount, *spec)?;
    }
    
    kwargs.assert_all_used()?;
    Ok(Value::from_object(TimeDeltaValue::new(total)))
}
```

This refactoring:
- Reduces the function from 85 lines to approximately 10 lines
- Maintains identical behaviour and error handling
- Preserves all component handling (weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds)
- Keeps the same function signature
- Ensures all existing tests continue to pass

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/stdlib/time/mod.rs (1)

52-138: Consolidated timedelta logic with ComponentSpec + add_component

This resolves the “excess arguments” smell and removes duplication. Overflow paths use checked_mul/checked_add with stable error messages.

As per coding guidelines

Also applies to: 163-178

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a595a8a and 87d1022.

📒 Files selected for processing (1)
  • src/stdlib/time/mod.rs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Keep functions small with single responsibility; apply command-query separation
Function documentation must include clear examples; test documentation should omit redundant examples
No single Rust code file may exceed 400 lines; split long switches/dispatch tables; move large test data to external files
Disallow Clippy warnings; code must be clean under clippy
Fix warnings emitted during tests in code, do not silence them
Extract helper functions when a function is too long; maintain separation of concerns and CQRS
Group many related parameters into well-named structs
Consider using Arc for large error values to reduce data returned
Every module must begin with a module-level //! comment describing purpose and utility
Document public APIs with Rustdoc /// comments so cargo doc can generate docs
Prefer immutable data; avoid unnecessary mut bindings
Use Result for error handling instead of panicking where feasible
Avoid unsafe code unless absolutely necessary; document any usage clearly
Place function attributes after doc comments
Do not use return in single-line functions
Use predicate functions for conditionals with more than two branches
Do not silence lints except as a last resort
When suppressing lints, scope narrowly and include a clear reason
Prefer #[expect(...)] over #[allow(...)] for lint handling
When functions are unused for certain features, gate with #[cfg]/#[cfg_attr]
Prefer .expect() over .unwrap()
Use concat!() for long string literals instead of escaping newlines with backslashes
Prefer single-line function bodies where appropriate (e.g., pub fn new(id: u64) -> Self { Self(id) })
Use cap-std for filesystem operations; avoid std::fs directly
Use camino for path handling; avoid std::path::PathBuf directly
Prefer semantic error enums deriving std::error::Error via thiserror for inspectable conditions
Use opaque eyre::Report only at the app boundary; do not expose in public APIs
Never export opaque error types from libraries; convert to domain...

Files:

  • src/stdlib/time/mod.rs

⚙️ CodeRabbit configuration file

**/*.rs: * Seek to keep the cyclomatic complexity of functions no more than 12.

  • Adhere to single responsibility and CQRS

  • Place function attributes after doc comments.

  • Do not use return in single-line functions.

  • Move conditionals with >2 branches into a predicate function.

  • Avoid unsafe unless absolutely necessary.

  • Every module must begin with a //! doc comment that explains the module's purpose and utility.

  • Comments and docs must follow en-GB-oxendict (-ize / -yse / -our) spelling and grammar

  • Lints must not be silenced except as a last resort.

    • #[allow] is forbidden.
    • Only narrowly scoped #[expect(lint, reason = "...")] is allowed.
    • No lint groups, no blanket or file-wide suppression.
    • Include FIXME: with link if a fix is expected.
  • Where code is only used by specific features, it must be conditionally compiled or a conditional expectation for unused_code applied.

  • Use rstest fixtures for shared setup and to avoid repetition between tests.

  • Replace duplicated tests with #[rstest(...)] parameterised cases.

  • Prefer mockall for mocks/stubs.

  • Prefer .expect() over .unwrap()

  • Ensure that any API or behavioural changes are reflected in the documentation in docs/

  • Ensure that any completed roadmap steps are recorded in the appropriate roadmap in docs/

  • Files must not exceed 400 lines in length

    • Large modules must be decomposed
    • Long match statements or dispatch tables should be decomposed by domain and collocated with targets
    • Large blocks of inline data (e.g., test fixtures, constants or templates) must be moved to external files and inlined at compile-time or loaded at run-time.
  • Environment access (env::set_var and env::remove_var) are always unsafe in Rust 2024 and MUST be marked as such

    • For testing of functionality depending upon environment variables, dependency injection and the mockable crate are the preferred option.
    • If mockable cannot be used, env mutations in...

Files:

  • src/stdlib/time/mod.rs
🔍 Remote MCP Ref

Summary of additional, review-relevant facts discovered

  • Dependency change: Cargo.toml adds runtime dependency "time" with features = ["formatting", "macros", "parsing", "serde"].

  • New stdlib wiring: src/stdlib/mod.rs now declares mod time; and register() calls time::register_functions(env) to expose the new helpers. (Check wrap into existing registration order to ensure no ordering regressions.)

  • Core implementation: src/stdlib/time/mod.rs implements now() and timedelta() helpers, timestamp and duration wrapper types, ISO‑8601 formatting/parsing, offset parsing, error handling, and attribute accessors (iso8601, unix_timestamp, offset, seconds, nanoseconds). This file is large and contains the functions flagged for complexity by CodeScene (parse_offset and format_duration_iso8601) and the add_component helper with many parameters — all referenced in the PR review notes. Focus review on correctness of:

    • parse_offset parsing semantics, range checks, and error messages (must preserve existing InvalidOperation messages used by tests).
    • format_duration_iso8601 correctness for edge cases (zero, negative durations, fractional seconds) and exact ISO‑8601 output.
    • add_component call sites in timedelta to ensure overflow/negative handling matches PR tests.
  • Tests added:

    • Unit tests in src/stdlib/time/tests.rs exercising now() offsets, invalid offsets, timedelta components (including negatives and overflow), and ISO‑8601 outputs. Use these to validate behavior if refactoring.
    • Feature tests in tests/features/stdlib_time.feature cover template rendering scenarios and error messages (overflow, invalid offset). These are the behavioral contract the implementation must satisfy.

Notes for reviewers (concise)

  • Any refactor must preserve exact ISO‑8601 string outputs and the exact InvalidOperation error messages asserted by the new tests/feature scenarios.
  • Pay special attention to overflow detection and sign handling in timedelta and offset parsing because tests assert both numeric properties and string representations.
  • CI/linters may fail on cyclomatic complexity; suggested helper extractions in PR comments target parse_offset, format_duration_iso8601, and add_component — verify tests pass after any such refactor.

Sources consulted

  • Cargo.toml (branch codex/implement-time-helpers-in-template-library)
  • src/stdlib/mod.rs (branch codex/implement-time-helpers-in-template-library)
  • src/stdlib/time/mod.rs (branch codex/implement-time-helpers-in-template-library)
  • src/stdlib/time/tests.rs (branch codex/implement-time-helpers-in-template-library)
  • tests/features/stdlib_time.feature (branch codex/implement-time-helpers-in-template-library)
⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: release / build-macos (x86_64-apple-darwin, macos-13, macos-x86_64, x86_64)
  • GitHub Check: release / build-macos (aarch64-apple-darwin, macos-14, macos-arm64, arm64)
  • GitHub Check: release / build-linux (x86_64-unknown-linux-gnu, linux-amd64, amd64)
  • GitHub Check: release / build-linux (aarch64-unknown-linux-gnu, linux-arm64, arm64)
  • GitHub Check: release / build-windows (x86_64-pc-windows-msvc, amd64, x64)
  • GitHub Check: release / build-windows (aarch64-pc-windows-msvc, arm64, arm64)
  • GitHub Check: Sourcery review
  • GitHub Check: build-test (ubuntu-latest, stable)
🔇 Additional comments (3)
src/stdlib/time/mod.rs (3)

1-7: Good module docs and purpose statement

Keep this. It satisfies the “module must begin with a //! doc comment” rule and explains the utility clearly.

As per coding guidelines


33-37: Registration looks correct

Register both helpers via closures capturing Kwargs by value and forwarding by reference. No stray kwargs due to assert_all_used().


220-242: Nice split; complexity is back under control

The extraction into format_time_components and finalize_duration_buffer reduces cyclomatic complexity while preserving edge cases (zero, negative, fractional seconds).

As per coding guidelines

Also applies to: 244-268, 270-281

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 87d1022 and 92d4bc3.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (2)
  • Cargo.toml (1 hunks)
  • src/stdlib/time/mod.rs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Keep functions small with single responsibility; apply command-query separation
Function documentation must include clear examples; test documentation should omit redundant examples
No single Rust code file may exceed 400 lines; split long switches/dispatch tables; move large test data to external files
Disallow Clippy warnings; code must be clean under clippy
Fix warnings emitted during tests in code, do not silence them
Extract helper functions when a function is too long; maintain separation of concerns and CQRS
Group many related parameters into well-named structs
Consider using Arc for large error values to reduce data returned
Every module must begin with a module-level //! comment describing purpose and utility
Document public APIs with Rustdoc /// comments so cargo doc can generate docs
Prefer immutable data; avoid unnecessary mut bindings
Use Result for error handling instead of panicking where feasible
Avoid unsafe code unless absolutely necessary; document any usage clearly
Place function attributes after doc comments
Do not use return in single-line functions
Use predicate functions for conditionals with more than two branches
Do not silence lints except as a last resort
When suppressing lints, scope narrowly and include a clear reason
Prefer #[expect(...)] over #[allow(...)] for lint handling
When functions are unused for certain features, gate with #[cfg]/#[cfg_attr]
Prefer .expect() over .unwrap()
Use concat!() for long string literals instead of escaping newlines with backslashes
Prefer single-line function bodies where appropriate (e.g., pub fn new(id: u64) -> Self { Self(id) })
Use cap-std for filesystem operations; avoid std::fs directly
Use camino for path handling; avoid std::path::PathBuf directly
Prefer semantic error enums deriving std::error::Error via thiserror for inspectable conditions
Use opaque eyre::Report only at the app boundary; do not expose in public APIs
Never export opaque error types from libraries; convert to domain...

Files:

  • src/stdlib/time/mod.rs

⚙️ CodeRabbit configuration file

**/*.rs: * Seek to keep the cyclomatic complexity of functions no more than 12.

  • Adhere to single responsibility and CQRS

  • Place function attributes after doc comments.

  • Do not use return in single-line functions.

  • Move conditionals with >2 branches into a predicate function.

  • Avoid unsafe unless absolutely necessary.

  • Every module must begin with a //! doc comment that explains the module's purpose and utility.

  • Comments and docs must follow en-GB-oxendict (-ize / -yse / -our) spelling and grammar

  • Lints must not be silenced except as a last resort.

    • #[allow] is forbidden.
    • Only narrowly scoped #[expect(lint, reason = "...")] is allowed.
    • No lint groups, no blanket or file-wide suppression.
    • Include FIXME: with link if a fix is expected.
  • Where code is only used by specific features, it must be conditionally compiled or a conditional expectation for unused_code applied.

  • Use rstest fixtures for shared setup and to avoid repetition between tests.

  • Replace duplicated tests with #[rstest(...)] parameterised cases.

  • Prefer mockall for mocks/stubs.

  • Prefer .expect() over .unwrap()

  • Ensure that any API or behavioural changes are reflected in the documentation in docs/

  • Ensure that any completed roadmap steps are recorded in the appropriate roadmap in docs/

  • Files must not exceed 400 lines in length

    • Large modules must be decomposed
    • Long match statements or dispatch tables should be decomposed by domain and collocated with targets
    • Large blocks of inline data (e.g., test fixtures, constants or templates) must be moved to external files and inlined at compile-time or loaded at run-time.
  • Environment access (env::set_var and env::remove_var) are always unsafe in Rust 2024 and MUST be marked as such

    • For testing of functionality depending upon environment variables, dependency injection and the mockable crate are the preferred option.
    • If mockable cannot be used, env mutations in...

Files:

  • src/stdlib/time/mod.rs
Cargo.toml

📄 CodeRabbit inference engine (AGENTS.md)

Cargo.toml: Use explicit version ranges in Cargo.toml and keep dependencies up to date
All dependencies must use SemVer caret requirements (e.g., "1.2.3")
Forbid wildcard (*) and open-ended (>=) version specifiers; use ~ only with documented, specific reasons

Files:

  • Cargo.toml
⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: release / build-windows (aarch64-pc-windows-msvc, arm64, arm64)
  • GitHub Check: release / build-windows (x86_64-pc-windows-msvc, amd64, x64)
  • GitHub Check: release / build-macos (x86_64-apple-darwin, macos-13, macos-x86_64, x86_64)
  • GitHub Check: Sourcery review
  • GitHub Check: build-test (ubuntu-latest, stable)
🔇 Additional comments (7)
Cargo.toml (1)

46-46: Pin time=0.3.44 with correct features — LGTM

Version and features align with usage and current release. No issues.

Also applies to: 51-51

src/stdlib/time/mod.rs (6)

230-306: ISO-8601 duration formatting decomposition — LGTM

Function is within complexity bounds and handles edge cases (zero, negative, fractional). No issues.


214-228: UTC offset string formatting — LGTM

Correct sign handling and SS omission when zero; safe with i32 range of UtcOffset.


161-191: Data-driven timedelta accumulation — LGTM

Checked arithmetic with clear overflow errors; CQRS and clippy-friendly. Solid improvement over repetitive code.

Also applies to: 185-195


197-212: Timestamp ISO-8601 with fractional-zero elision — LGTM

Infallible fallback path is appropriate; zero-fraction trimming is safe and targeted.


328-341: Attribute exposure surface — LGTM

Attributes match PR objectives (iso8601, unix_timestamp/offset, seconds/nanoseconds). Interface is minimal and predictable.

Also applies to: 365-378


39-50: Verify render-level determinism for now()
Guarantee that multiple calls to now() within a single render return the same instant. If determinism is implemented elsewhere (e.g. timestamp captured once per render), add a brief comment referencing that; otherwise capture the clock once at the start of the render.

Comment thread src/stdlib/time/mod.rs Outdated
Comment thread src/stdlib/time/mod.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 92d4bc3 and fc3f8b8.

📒 Files selected for processing (1)
  • src/stdlib/time/mod.rs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Keep functions small with single responsibility; apply command-query separation
Function documentation must include clear examples; test documentation should omit redundant examples
No single Rust code file may exceed 400 lines; split long switches/dispatch tables; move large test data to external files
Disallow Clippy warnings; code must be clean under clippy
Fix warnings emitted during tests in code, do not silence them
Extract helper functions when a function is too long; maintain separation of concerns and CQRS
Group many related parameters into well-named structs
Consider using Arc for large error values to reduce data returned
Every module must begin with a module-level //! comment describing purpose and utility
Document public APIs with Rustdoc /// comments so cargo doc can generate docs
Prefer immutable data; avoid unnecessary mut bindings
Use Result for error handling instead of panicking where feasible
Avoid unsafe code unless absolutely necessary; document any usage clearly
Place function attributes after doc comments
Do not use return in single-line functions
Use predicate functions for conditionals with more than two branches
Do not silence lints except as a last resort
When suppressing lints, scope narrowly and include a clear reason
Prefer #[expect(...)] over #[allow(...)] for lint handling
When functions are unused for certain features, gate with #[cfg]/#[cfg_attr]
Prefer .expect() over .unwrap()
Use concat!() for long string literals instead of escaping newlines with backslashes
Prefer single-line function bodies where appropriate (e.g., pub fn new(id: u64) -> Self { Self(id) })
Use cap-std for filesystem operations; avoid std::fs directly
Use camino for path handling; avoid std::path::PathBuf directly
Prefer semantic error enums deriving std::error::Error via thiserror for inspectable conditions
Use opaque eyre::Report only at the app boundary; do not expose in public APIs
Never export opaque error types from libraries; convert to domain...

Files:

  • src/stdlib/time/mod.rs

⚙️ CodeRabbit configuration file

**/*.rs: * Seek to keep the cyclomatic complexity of functions no more than 12.

  • Adhere to single responsibility and CQRS

  • Place function attributes after doc comments.

  • Do not use return in single-line functions.

  • Move conditionals with >2 branches into a predicate function.

  • Avoid unsafe unless absolutely necessary.

  • Every module must begin with a //! doc comment that explains the module's purpose and utility.

  • Comments and docs must follow en-GB-oxendict (-ize / -yse / -our) spelling and grammar

  • Lints must not be silenced except as a last resort.

    • #[allow] is forbidden.
    • Only narrowly scoped #[expect(lint, reason = "...")] is allowed.
    • No lint groups, no blanket or file-wide suppression.
    • Include FIXME: with link if a fix is expected.
  • Where code is only used by specific features, it must be conditionally compiled or a conditional expectation for unused_code applied.

  • Use rstest fixtures for shared setup and to avoid repetition between tests.

  • Replace duplicated tests with #[rstest(...)] parameterised cases.

  • Prefer mockall for mocks/stubs.

  • Prefer .expect() over .unwrap()

  • Ensure that any API or behavioural changes are reflected in the documentation in docs/

  • Ensure that any completed roadmap steps are recorded in the appropriate roadmap in docs/

  • Files must not exceed 400 lines in length

    • Large modules must be decomposed
    • Long match statements or dispatch tables should be decomposed by domain and collocated with targets
    • Large blocks of inline data (e.g., test fixtures, constants or templates) must be moved to external files and inlined at compile-time or loaded at run-time.
  • Environment access (env::set_var and env::remove_var) are always unsafe in Rust 2024 and MUST be marked as such

    • For testing of functionality depending upon environment variables, dependency injection and the mockable crate are the preferred option.
    • If mockable cannot be used, env mutations in...

Files:

  • src/stdlib/time/mod.rs
⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: release / build-macos (aarch64-apple-darwin, macos-14, macos-arm64, arm64)
  • GitHub Check: release / build-macos (x86_64-apple-darwin, macos-13, macos-x86_64, x86_64)
  • GitHub Check: release / build-windows (aarch64-pc-windows-msvc, arm64, arm64)
  • GitHub Check: release / build-windows (x86_64-pc-windows-msvc, amd64, x64)
  • GitHub Check: release / build-linux (x86_64-unknown-linux-gnu, linux-amd64, amd64)
  • GitHub Check: Sourcery review
  • GitHub Check: build-test (ubuntu-latest, stable)
🔇 Additional comments (3)
src/stdlib/time/mod.rs (3)

52-59: Parse offset implementation is clean and correct.

The function now uses UtcOffset::parse as suggested in previous reviews, significantly reducing complexity whilst preserving the same error surface. The explicit handling of "Z" is clear and correct.

Based on learnings


68-176: Data-driven approach eliminates repetition effectively.

The use of COMPONENT_SPECS with a unified add_component helper successfully addresses previous feedback about code duplication. The ComponentSpec struct appropriately groups related parameters, and the iteration in timedelta is clean and maintainable.

Based on learnings


211-272: Duration formatting is well-decomposed into focused helpers.

The split into format_duration_iso8601, format_time_components, and finalize_duration_buffer maintains single responsibility and keeps complexity low. The implementation correctly handles ISO-8601 edge cases including zero durations, negative signs, and fractional seconds.

Comment thread src/stdlib/time/mod.rs Outdated
Comment thread src/stdlib/time/mod.rs
Extract a predicate for detecting timezone suffixes when trimming fractional seconds, tighten the offset parser to require an explicit sign before delegating to the time crate, and expand the expect message for fractional-second formatting.
@leynos leynos merged commit 099d6f4 into main Oct 8, 2025
14 of 16 checks passed
@leynos leynos deleted the codex/implement-time-helpers-in-template-library branch October 8, 2025 21:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant